#ifndef CL_BENCHMARK_HPP
#define CL_BENCHMARK_HPP

#if !defined(CL_CALLBACK)
#if defined(_WIN32)
#define CL_CALLBACK __stdcall
#else
#define CL_CALLBACK
#endif
#endif

#include <QObject>
#include <cstddef>
#include <string>

struct _cl_program;
typedef struct _cl_program *  cl_program;

namespace cl {

    class Device;
    class Context;
    class Program;

    /** @brief The Benchmark class */
    class Benchmark : public QObject {

        Q_OBJECT

    public:

        /** @brief The NVector enum */
        enum NVector { N1, N2, N4, N8 /*, N16 */ };

        /** @brief BUILD_SUCCESS build status */
        static const int BUILD_SUCCESS;
        /** @brief BUILD_NONE build status */
        static const int BUILD_NONE;
        /** @brief BUILD_ERROR build status */
        static const int BUILD_ERROR;
        /** @brief BUILD_IN_PROGRESS */
        static const int BUILD_IN_PROGRESS;

        /** @brief KERNEL_FP32 float precision benchmark test name */
        static const char *KERNEL_FP32;
        /** @brief KERNEL_FP64 double precision benchmark test name */
        static const char *KERNEL_FP64;

        /** @brief Benchmark test of the given device
         * @param device selection
         * @param parent */
        explicit Benchmark(const Device &device, QObject *parent = 0);
        virtual ~Benchmark(void);

        /** @brief loadProgram of the benchmark. Emits the programBuilt signal.
         * @param hasDouble = true for fp64 build */
        void loadProgram(bool hasDouble = false);

        /** @brief hasDouble equals true if device has fp64 capability */
        bool hasDouble(void);

        /** @brief runKernel of given benchmark test name and vector dimension
         * @param name of the test kernel {KERNEL_FP32|KERNEL_FP64}
         * @param dimension of the vector {N1|N2|N4|N8} */
        void runTest(const char *name, NVector dimension = N1);

        /** @brief getProfilingTime retrieves the needed time to execute a test
         *         measured in nanoseconds */
        double getProfilingTime(void);

        /** @brief getProfilingPerformance retrieves the achieved performance of
         *         a test measured in Flop/s */
        double getProfilingPerformance(void);

    signals:

        void programBuilt(int status, const char *options, const char *log);
        void contextError(const char *errorInfo);

    private:

        static void CL_CALLBACK contextNotify(
                const char *errorInfo, const void *, std::size_t, void *object);
        static void CL_CALLBACK buildNotify(cl_program, void *object);

        Program *createProgramFormSource(const char *sourceFile);
        void buildProgram(Program *program);

        std::string getVectorKernelName(const std::string &kernelName,
                                        NVector dimension);

        Context *context;
        Program *program_fp32;
        Program *program_fp64;
        bool hasDoubleOption;
        double profilingTime;
        double profilingPerformance;
    };
}

#endif // CL_BENCHMARK_HPP
